﻿/*
 * PsTFS is a set of PowerShell commands to manage and use Team Foundation Server. PowerShell 
 * effectiveness is combined with the power of TFS API to maximizes TFS capabilities.
 * 
 * Creator : Choulant Noham
 * Mail : choulant@gmail.com
 * Blog : http://choulant.blogspot.com
 * 
 */

using System;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Provider;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using PsTFS.Methodes;
using PsTFS.Objects;
using System.Collections.Generic;

namespace PsTFS.Provider
{
    #region TFSProvider

    /// <summary>
    /// This PS provider is a navigation provider that navigates an 
    /// Team Foundation Server.
    /// </summary>
    /// <remarks>
    /// </remarks>
    [CmdletProvider("TFSProvider", ProviderCapabilities.None)]
    public class TFSProvider : NavigationCmdletProvider
    {
        public static string s_root { get; set; }

        #region Drive Manipulation

        /// <summary>
        /// This override creates a new drive.  It creates a connection 
        /// to the database file and sets the Connection property in the 
        /// PSDriveInfo object.
        /// </summary>
        /// <param name="drive">
        /// Information describing the drive to add.
        /// </param>
        /// <returns>The added drive.</returns>
        protected override PSDriveInfo NewDrive(PSDriveInfo drive)
        {
            // Check if the drive object is null.
            if (drive == null)
            {
                WriteError(new ErrorRecord(
                           new ArgumentNullException("drive"),
                           "NullDrive",
                           ErrorCategory.InvalidArgument,
                           null)
                          );

                return null;
            }

            // Check if the drive root is not null or empty.
            if (String.IsNullOrEmpty(drive.Root))
            {
                WriteError(new ErrorRecord(
                           new ArgumentException("drive.Root"),
                           "NoRoot",
                           ErrorCategory.InvalidArgument,
                           drive)
                          );

                return null;
            }
            // Create a new drive and create an ODBC connection to the new drive.
            TFSDriveInfo tfsDriveInfo = new TFSDriveInfo(drive);

            TfsTeamProjectCollection tfs = null;

            tfs = new TfsTeamProjectCollection(new Uri(drive.Description));

            tfs.Authenticate();

            Context.Instance.TFS = tfs;
            Context.Instance.SetProject(drive.Root);

            tfsDriveInfo.Tfs = tfs;


            return tfsDriveInfo;
        } // NewDrive

        /// <summary>
        /// This override removes a drive from the provider.
        /// </summary>
        /// <param name="drive">The drive to remove.</param>
        /// <returns>The drive removed.</returns>
        protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
        {
            // Check if drive object is null
            if (drive == null)
            {
                WriteError(new ErrorRecord(
                           new ArgumentNullException("drive"),
                           "NullDrive",
                           ErrorCategory.InvalidArgument,
                           drive)
                          );

                return null;
            }

            // Close the ODBC connection to the drive.
            TFSDriveInfo tfsDriveInfo = drive as TFSDriveInfo;

            if (tfsDriveInfo == null)
            {
                return null;
            }
            tfsDriveInfo.Tfs.Dispose();
            Context.ClearContext();

            return tfsDriveInfo;
        } // RemoveDrive

        #endregion Drive Manipulation

        #region Item Methods

        /// <summary>
        /// This override Retrieves an item using the specified path.
        /// </summary>
        /// <param name="path">The path to the item to return.</param>
        protected override void GetItem(string path)
        {
            // Check if the specified path is a drive.
            if (PathIsDrive(path))
            {
                WriteItemObject(this.PSDriveInfo, path, true);
                return;
            }
            if (PathIsProject(path))
            {
                WriteItemObject(Context.Instance.Project, path, true);
                return;
            }
        } // GetItem

        /// <summary>
        /// This override checks to see if the specified item exists.
        /// </summary>
        /// <param name="path">The path to the item to verify.</param>
        /// <returns>True if the item is found.</returns>
        protected override bool ItemExists(string path)
        {
            //path = path.Replace(this.PSDriveInfo.Root.Replace(@"//",@"\\"), "");

            // Checks if the path is null or empty.
            if (String.IsNullOrEmpty(path))
            {
                WriteError(
                           new ErrorRecord(new ArgumentException("path"),
                           "NoPath",
                           ErrorCategory.InvalidArgument, path)
                          );
                return false;
            }
            // Checks if the specifiedpath is a drive.
            if (PathIsDrive(path))
            {
                return true;
            }
            if(PathIsProject(path) )
            {
                return true;
            }
            if (PathIsSourceControl(path))
            {
                return true;
            }
            if (PathIsWorkitem(path))
            {
                return true;
            }

            return false;
        } // ItemExists

        /// <summary>
        /// This override test to see if the specified path is syntactically valid.
        /// </summary>
        /// <param name="path">The path to validate.</param>
        /// <returns>True if the specified path is valid.</returns>
        protected override bool IsValidPath(string path)
        {
            bool result = true;

            // Checks if the path is null or empty.
            if (String.IsNullOrEmpty(path))
            {
                result = false;
            }

            // Converts all separators in the path to a uniform one.
            path = NormalizePath(path);

            // split the path into individual chunks
            string[] pathChunks = path.Split(pathSeparator.ToCharArray());

            foreach (string pathChunk in pathChunks)
            {
                if (pathChunk.Length == 0)
                    result = false;
            }
            return result;
        } // IsValidPath

        #endregion Item Overloads

        #region Container Methods

        /// <summary>
        /// This override gets the child items, returning either the tables 
        /// in the database or the rows of the tables.
        /// </summary>
        /// <param name="path">The path to the parent</param>
        /// <param name="recurse">When true, this method returns all child 
        /// items recursively.
        /// </param>
        protected override void GetChildItems(string path, bool recurse)
        {
            // If the specified path is a drive, then the children in the path are 
            // table, and all tables in the drive are returned.
            if (PathIsDrive(path))
            {

                var l = from t in Methode.GetProjects()
                        select t.Name; 
                

                WriteItemObject(l, path, true);
            }

            if (PathIsProject(path))
            {
                if (PathIsSourceControl(path))
                {
                    ItemSet itemSet = MethodService.VersionControlServer().GetItems(path, RecursionType.OneLevel);

                    var l = from t in itemSet.Items
                            select t.ServerItem;

                    WriteItemObject(l, path, false);
                }
                if (PathIsWorkitem(path))
                {
                    WorkItemCollection wis = Context.Instance.WorkItemStore.Query("Select [System.ID] from WorkItem WHERE [System.TeamProject] = '"
                        + Context.Instance.Project.Name + "'");

                    WriteItemObject(wis, path, false);
                }
                else
                {
                    List<string> l = new List<string>();
                    l.Add("Workitem");
                    l.Add("SourceControl");
                    l.Add("Build");
                    l.Add("Document");
                    WriteItemObject(l, path, true);
                }
            }
        }
        private bool PathIsSourceControl(string path)
        {
            string[] pathChunks = ChunkPath(path);

            if (pathChunks[pathChunks.Length - 1].Equals("SourceControl",StringComparison.InvariantCultureIgnoreCase))
                return true;
            else
                return false;
        }

        //TODO: Refactor
        private bool PathIsProject(string path)
        {
            string[] pathChunks = ChunkPath(path);
            if (pathChunks[0] == "")
            {
                Context.Instance.SetProject(pathChunks[1]);
                if (Context.Instance.Project != null)
                    return true;
                else
                    return false;
            }
            else
            {
                Context.Instance.SetProject(pathChunks[0]);
                if (Methode.GetProject(pathChunks[0]) != null)
                    return true;
                else
                    return false;
            }
        } // GetChildItems
        private bool PathIsWorkitem(string path)
        {
            string[] pathChunks = ChunkPath(path);
            if (pathChunks[pathChunks.Length - 1].Equals("WorkItem", StringComparison.InvariantCultureIgnoreCase))
                return true;
            else
                return false;
            
        } // GetChildItems

        /// <summary>
        /// This override gets child item names, returning the names of all child items.
        /// </summary>
        /// <param name="path">The root path.</param>
        /// <param name="returnContainers">Not used.</param>
        protected override void GetChildNames(string path,
                                              ReturnContainers returnContainers)
        {
            // If the specified path is a drive, then the child items are
            // tables. Return the names of all the tables in the drive.
            if (PathIsDrive(path))
            {
                foreach (TeamProject project in Methode.GetProjects())
                {
                    WriteItemObject(project.Name, path, true);
                } // foreach (DatabaseTableInfo...
            } // if (PathIsDrive...
            else
            {
                if (PathIsProject(path))
                {
                    if (PathIsSourceControl(path))
                    {
                        ItemSet itemSet = MethodService.VersionControlServer().GetItems(path, RecursionType.OneLevel);

                        var l = from t in itemSet.Items
                                select t.ServerItem;

                        WriteItemObject(l, path, false);
                    }
                    if (PathIsWorkitem(path))
                    {
                        WorkItemCollection wis = Context.Instance.WorkItemStore.Query("Select [System.ID] from WorkItem WHERE [System.TeamProject] = '"
                            + Context.Instance.Project.Name + "'");

                        WriteItemObject(wis, path, false);
                    }
                    else
                    {
                        List<string> l = new List<string>();
                        l.Add("Workitem");
                        l.Add("SourceControl");
                        l.Add("Build");
                        l.Add("Document");
                        WriteItemObject(l, path, true);
                    }
                }
            } // else
        } // GetChildNames

        /// <summary>
        /// This override determines if the specified path has child items.
        /// </summary>
        /// <param name="path">The path to examine.</param>
        /// <returns>
        /// True if the specified path has child items.
        /// </returns>
        protected override bool HasChildItems(string path)
        {
            if (PathIsDrive(path))
                return true;

            return (ChunkPath(path).Length == 1);
        } // HasChildItems

        /// <summary>
        /// This override creates a new item at the specified path.
        /// </summary>
        /// <param name="path">
        /// The path to the new item.
        /// </param>
        /// <param name="type">
        /// Type for the object to create. "Table" for creating a new table and
        /// "Row" for creating a new row in a table.
        /// </param>
        /// <param name="newItemValue">
        /// Object for creating new instance of a type at the specified path. For
        /// creating a "Table" the object parameter is ignored and for creating
        /// a "Row" the object must be of type string which will contain comma 
        /// separated values of the rows to insert.
        /// </param>
        protected override void NewItem(string path, string type,
                                        object newItemValue)
        {
            if (PathIsDrive(path))
            {
                throw new
                ArgumentException("Path must be a either a project");
            }

            // The leaf of the path will contain the name of the table to create
            // and a row number for creating a row. 
            string[] pathChunks = ChunkPath(path);

            string ProjectName = pathChunks[0];

            if (!ProjectNameIsValid(ProjectName))
            {
                return;
            }

            //string rowNumberAsStr = null;
            //int rowNumber = -1;
            //if (pathChunks.Length > 1)
            //{
            //    rowNumberAsStr = pathChunks[1];
            //    rowNumber = SafeConvertRowNumber(rowNumberAsStr);
            //}
            
        } // NewItem

        #endregion Container Methodfs

        #region Navigation

        /// <summary>
        /// This overide determine if the path specified is that of a container.
        /// </summary>
        /// <param name="path">The path to check.</param>
        /// <returns>True if the path specifies a container.</returns>
        protected override bool IsItemContainer(string path)
        {
            if (PathIsDrive(path))
            {
                return true;
            }

            if(PathIsProject(path))
            {
                return true;
            }

            return false;
        } // End IsItemContainer.

        #endregion Navigation

        #region Helper Methods

        /// <summary>
        /// This override checks if a given path is actually a drive name.
        /// </summary>
        /// <param name="path">The path to check.</param>
        /// <returns>
        /// True if the path given represents a drive, false otherwise.
        /// </returns>
        private bool PathIsDrive(string path)
        {
            // Remove the drive name and first path separator.  If the 
            // path is reduced to nothing, it is a drive. Also if its
            // just a drive then there wont be any path separators
            if (String.IsNullOrEmpty(
                            path.Replace(this.PSDriveInfo.Root, "")) ||
                            String.IsNullOrEmpty(
                            path.Replace(this.PSDriveInfo.Root + pathSeparator, ""))

               )
            {
                return true;
            }
            else
            {
                return false;
            }
        } // PathIsDrive

        /// <summary>
        /// Breaks up the path into individual elements.
        /// </summary>
        /// <param name="path">The path to split.</param>
        /// <returns>An array of path segments.</returns>
        private string[] ChunkPath(string path)
        {
            // Return the path with the drive name and first path 
            // separator character removed, split by the path separator.
            string pathNoDrive = path.Replace(this.PSDriveInfo.Root, "");
            pathNoDrive = pathNoDrive.Replace(this.PSDriveInfo.Root + pathSeparator, "");

            return pathNoDrive.Split(pathSeparator.ToCharArray());
        } // ChunkPath

        /// <summary>
        /// Adapts the path, making sure the correct path separator
        /// character is used.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private string NormalizePath(string path)
        {
            string result = path;

            if (!String.IsNullOrEmpty(path))
                result = path.Replace("/", pathSeparator);

            return result;
        } // NormalizePath

      
        

        /// <summary>
        /// Checks if a table name is valid
        /// </summary>
        /// <param name="tableName">Table name to validate</param>
        /// <remarks>Helps to check for SQL injection attacks</remarks>
        private bool ProjectNameIsValid(string tableName)
        {
            Regex exp = new Regex(pattern, RegexOptions.Compiled);

            if (exp.IsMatch(tableName))
            {
                return true;
            }
            WriteError(new ErrorRecord(
                     new ArgumentException("Project name not valid"), "InvalidArgument",
                     ErrorCategory.InvalidArgument, tableName)
                    );
            return false;
        } // TableNameIsValid

        #endregion Helper Methods

        #region Private Properties

        private string pathSeparator = "\\";
        private static string pattern = "[0-9A-Za-z_]";

        #endregion Private Properties

    } // AccessDBProvider

    #endregion AccessDBProvider

    #region Helper Classes

    #region TFSDriveInfo

    /// <summary>
    /// Any state associated with the drive should be held here.
    /// In this case, it's the connection to the database.
    /// </summary>
    internal class TFSDriveInfo : PSDriveInfo
    {
        public  TfsTeamProjectCollection Tfs;

        /// <summary>
        /// Constructor that takes one argument.
        /// </summary>
        /// <param name="driveInfo">Drive provided by this provider</param>
        public TFSDriveInfo(PSDriveInfo driveInfo)
            : base(driveInfo)
        { }

    } // class TFSDriveInfo

    #endregion TFSDriveInfo

    #endregion Helper Classes

}